C++ new delete (new[] delete[]) operator overloading needs attention

  • 2020-04-02 02:54:20
  • OfStack

Overloading the new, delete (new[], delete[]) operators requires attention:

The overloaded new, delete (or new[], delete[]) operators must be static member functions of the class (it's easy to understand why it has to be a static member function, since the object is not built when the new operator is called) or a global function. The prototype of the function is as follows:


void* operator new(size_t size) throw(std::bad_alloc);
//The size here is the total size of the allocated memory
void* operator new[](size_t size) throw(std::bad_alloc);
 
void operator delete(void* p) throw();
void operator delete[](void* p) throw();
 
void operator delete(void* p, size_t size) throw();
//Unlike the parameter size of new[], the size here is not the total size of the freed memory
void operator delete[](void* p, size_t size) throw();

In addition, we can override the new, delete (or new[], delete[]) operators with different parameters, such as:


//The first argument is still size_t
void* operator new(size_t size, const char* file, int line);
//The use of this operator is
string* str = new(__FILE__, __LINE__) string;

Overloading of the global new and delete (or new [], delete []) operator will change all the default allocation behavior (including distribution behavior of a class), so must be careful to use, if both libraries such as the new global overloading, then there will be a link error (duplicated symbol link error). The new, delete (or new[], delete[]) operators defined in the class only affect the class and the derived class.

Many people are completely unaware that operator new, operator delete, operator new[], and operator delete[] member functions are inherited (although they are static functions). Sometimes, we just want to set the custom operator new member function for the specified class, and we don't want to affect the subclass's work. Effective C++ Third Edition offers the following solution:


void * Base::operator new(std::size_t size) throw(std::bad_alloc)
{
    //If the size is not the base class size
    if (size != sizeof(Base))
        //Invokes the standard new operator
        return ::operator new(size);
 
    The custom size is the allocation handling of the base class size
}

This is done on the premise that the subclass must be larger than the parent class.

For operator new[], it is difficult to check whether the parent or subclass invoked the operator in the above way. With the arguments of the operator new[] operator, we cannot know the number of allocated elements, or the size of each allocated element. The operator new[] argument size_t indicates that the size of the memory allocation may be greater than the sum of the memory sizes of the elements to be allocated, because dynamic memory allocation may allocate additional space to hold the number of array elements.

2. Compatible with the default new and delete error handling methods

This is not an easy thing to do (see Effective C++ Third Edition Item 51 for details). Operator new is usually written like this:


//Multithreaded access is not considered here
void* operator new(std::size_t size) throw(std::bad_alloc)
{
    using namespace std;
 
    //New must also return a valid
pointer when size == 0     if (size == 0)
        size = 1;
 
    while (true) {
 
        Attempt to allocate memory
 
        if ( Memory allocation successful )
            return ( The address of successfully allocated memory );
 
        //When the memory allocation fails, look for the current new-handling function
        //Since there is no way to directly obtain the new-handling function, you can only do this
        new_handler globalHandler = set_new_handler(0);
        set_new_handler(globalHandler);
 
        //If a new-handling function exists, call
        if (globalHandler) (*globalHandler)();
        //The absence of a new-handling function throws an exception
        else throw std::bad_alloc();
    }
}

Here are some things to note: operator new can accept memory allocation of size 0 and return a valid pointer; If there is a new-handling function, it is called when the memory allocation fails and the memory allocation is tried again. A bad_alloc exception is thrown if there is no new-handling function failure.

Note that the memory allocation for the new-handling function runs an infinite loop once it is set. To avoid an infinite loop, the new-handling function must do one of the following (see Effective C++ Third Edition Item 49 for details) : Make more memory available, set another working new-handler, remove the current new handler, throw an exception (bad_alloc or inherited from bad_alloc), and call abort() or exit() directly.

The exception handling for operator delete is simpler, just the safe null pointer to delete:


void operator delete(void *rawMemory) throw()
{
    //The operator accepts a null pointer
    if (rawMemory == 0) return;
 
    Free memory
}

Polymorphic problems (see ISO/IEC 14882 for details)

We have talked about the inheritance of the new, delete (new[], delete[]) operators. Here, we will discuss the problem of polymorphism. Obviously, we only need to discuss the delete, delete[] operators:


struct B {
    virtual ~B();
    void operator delete(void*, size_t);
};
 
struct D : B {
    void operator delete(void*);
};
 
void f()
{
    B* bp = new D;
    delete bp;  //1: uses D::operator delete(void*)
}

From the above example, we can see that the operator delete operator of D is correctly invoked when delete. But again, the delete[] operator does not work properly (because the check for the delete[] operator is static) :


struct B {
    virtual ~B();
    void operator delete[](void*, size_t);
};
 
struct D : B {
    void operator delete[](void*, size_t);
};
 
void f(int i)
{
    D* dp = new D[i];
    delete [] dp;  //uses D::operator delete[](void*, size_t)
    B* bp = new D[i];
    delete[] bp;  //undefined behavior
}


Related articles: